/*
 * Writes a diffdump file specialised for the OS, if it doesn't exist already
 * and runs the diffdump shell command, extracting the data from its output
 */
package monash.med.crystallography.DataSetTools.DiffdumpXML;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author steve a
 */
public class DiffdumpOut {

    private String fileName;
    private String diffFileName;
    private String diffVersion;
    private String imageType = "Not Set";
    private Date collectionDate;
    private double exposureTime = -1;
    private String detectorsn = "Not Set";
    private double wavelength = -1;
    private double beamCenterX = -1;
    private double beamCenterY = -1;
    private double distanceToDetector = -1;
    private double imageSizeX = -1;
    private double imageSizeY = -1;
    private double pixelSizeX = 1;
    private double pixelSizeY = 1;
    private double oscillationStart = -1;
    private double oscillationEnd = -1;
    private double twoThetaValue = -1;
    private double interval = -1;
    private String osname;
    private String osarch;
    
    // finds jar executable path, cross-platform compatible.. MUST EXECUTE THIS PROGRAM FROM A JAR
     public String locate() throws ClassNotFoundException, UnsupportedEncodingException {

        final URL location;
        final String classLocation = this.getClass().getName().replace('.', '/') + ".class";
        final ClassLoader loader = this.getClass().getClassLoader();
        if( loader == null )
        {
          location = ClassLoader.getSystemResource(classLocation);
        }
        else
        {
          location = loader.getResource(classLocation);
        }
        if( location != null ) 
        {
          Pattern p = Pattern.compile( "^.*:(.*)!.*$" ) ;

          Matcher m = p.matcher( URLDecoder.decode(location.getPath(), "UTF-8") );
          if( m.find() )
          {
          
            return new File(m.group( 1 )).getParent();
          }
          else
          {
            throw new ClassNotFoundException( "Cannot parse location of '" + location + "'.  Probably not loaded from a Jar" ) ;
          }
        } 
        else 
        {
          throw new ClassNotFoundException( "Cannot find class '" + this.getClass().getName() + " using the classloader" ) ;
        }
  }  
     
    // 4 different types of diffdump, only one will survive
    private void determineDiffdumpVersion()
    {
       
        if(osname.equals("Mac OS X"))
        {
            this.diffFileName = "diffdump";
            if(osarch.equals("i386"))
            {
                this.diffVersion = "mac_386";
            }
            else
            {
                this.diffVersion = "mac_ppc";
            }
        }
        else if(osname.equals("Linux"))
        {
            this.diffFileName = "diffdump";
            this.diffVersion = "linux_386";
        }
        else if(osname.contains("Windows"))
        {
            this.diffVersion = "win32";
            this.diffFileName = "diffdump.exe";
        }
    }
    
    private void createDiffdump()
    {
        determineDiffdumpVersion();

        // get the diffdump executable from the jar
        ClassLoader cl = this.getClass().getClassLoader();
        URL diffdump = cl.getResource("diffdump/" + this.diffVersion + "/" + this.diffFileName);

        // enough bytes in the buffer to create diffdump
        byte[] buf = new byte[5000000];

        int len = 0;

        try{

            // will fail if not run from a jar..
            JarURLConnection uc = (JarURLConnection)diffdump.openConnection();  

            uc.connect();


            InputStream in = new BufferedInputStream(uc.getInputStream());

            len = in.read(buf);

            in.close();             
            
            try{
                String diffFilePath = locate() + File.separator + this.diffFileName;
                File tmpFile = new File(diffFilePath);           
            
                // if diffdump doesn't already exist in the directory, then write it
                if(!tmpFile.exists())
                {
                    FileOutputStream output = new FileOutputStream(tmpFile);

                    output.write(buf, 0, len);  

                    output.close();  

                    if(!osname.contains("Windows")) // if it's not windows then you need to chmod it for x
                    {
                        String[] chCommand = {"sh", "-c", "chmod +x \"" + diffFilePath + "\""};
                        runCommand(chCommand);                                    
                    }

                }
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }

        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        
    }

    private String execute() {

        /* Execute the executive native program, and get its output message */
        
        String outputMessage = "";
        
        try
        {
        String diffFilePath = locate() + File.separator + this.diffFileName;
        
        
        if(osname.contains("Windows"))
        {
            // no need to shell it if windows
            String[] command = {"\"" + diffFilePath + "\" \"" + this.fileName + "\""};
            outputMessage = runCommand(command);
        }
        else
        {
            String[] command = {"sh", "-c", "\"" + diffFilePath + "\" \"" + this.fileName + "\""}; 
            outputMessage = runCommand(command);
        }
        }
        catch(Exception e)
        {
            outputMessage = "mass fail";
            e.printStackTrace();
        }
        
        return outputMessage;
    }

        private String runCommand(String[] command) { 
            
        try {
                Process ps = Runtime.getRuntime().exec(command);
                return loadStream(ps.getInputStream());
                
        } catch (IOException ioe) {

                ioe.printStackTrace();
        }

        return null;

    }

    // gets the program output
    private String loadStream(InputStream input) throws IOException {

        int count;

        int BUFFER = 20;

        byte[] data = new byte[BUFFER];

        input = new BufferedInputStream(input);

        StringBuffer buff = new StringBuffer();

        while ((count = input.read(data, 0, BUFFER)) != -1)

            buff = buff.append(new String(data, 0, count));

        return buff.toString();

    }    
    
    public DiffdumpOut(String fileName) throws Exception {
        
        this.fileName = fileName;
        osname = System.getProperty("os.name");
        osarch = System.getProperty("os.arch");
        
        createDiffdump();
        String output = execute();

        
        if(!output.startsWith("Exception:"))
        {

            String[] byline = output.split("\n");

            // begin diffdump output analysis
            for(int i = 0; i < byline.length; i++)
            {
                String line = byline[i];
                
                String[] splitLine = line.split(":");

                if(i == 0) // image type
                {

                   setImageType(splitLine[1].trim());                
                }
                // catch all supported date formats
                else if(i == 1) // date: yyyy-mm- dd returns timestamp (ms)
                {

                    String[] dateLine = splitLine[1].split("-");
                    if(!splitLine[1].trim().equals(""))
                    {
                        try{
                            GregorianCalendar gc = new GregorianCalendar(
                                    Integer.parseInt(dateLine[0].trim()),
                                    Integer.parseInt(dateLine[1].trim()),
                                    Integer.parseInt(dateLine[2].trim()));

                            setCollectionDate(gc.getTime());                    
                        }
                        catch(NumberFormatException n)
                        {
                            try{
                            GregorianCalendar gc = new GregorianCalendar();
                            gc.setTimeInMillis(Math.round(Double.valueOf(splitLine[1].trim())));
                            setCollectionDate(gc.getTime());
                            }
                            catch(NumberFormatException e)
                            {
                                //Collection date : Thu Aug 31 14:19:45 2006
                                SimpleDateFormat format =
                                   new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy");

                                String[] joinDate = line.split(":", 2);                         

                                setCollectionDate(format.parse(joinDate[1].trim()));
                            }
                        }
                    }

                }
                else if(i == 2)
                {

                    String seconds = splitLine[1].substring(0, splitLine[1].length()-2).trim();

                    setExposureTime(Double.valueOf(seconds));
                }
                else if(i == 3)
                {

                   setDetectorsn(splitLine[1].trim()); 
                }
                else if(i == 4)
                {

                    String length = splitLine[1].substring(0, splitLine[1].length()-4).trim();

                    setWavelength(Double.valueOf(length));
                }
                else if(i == 5)
                {

                    String [] splitBeam = splitLine[1].split(",");
                    setBeamCenterX(Double.valueOf(splitBeam[0].trim().substring(1, splitBeam[0].length()-3)));
                    setBeamCenterY(Double.valueOf(splitBeam[1].trim().substring(0, splitBeam[0].length()-4)));
                }
                else if(i == 6)
                {

                    String distance = splitLine[1].substring(0, splitLine[1].length()-3).trim();

                    setDistanceToDetector(Double.valueOf(distance));                
                }
                else if(i == 7)
                {

                    String [] splitImage = splitLine[1].split(",");
                    setImageSizeX(Double.valueOf(splitImage[0].trim().substring(1, splitImage[0].length()-3)));
                    setImageSizeY(Double.valueOf(splitImage[1].trim().substring(0, splitImage[0].length()-4)));
                }
                else if(i == 8)
                {

                    String [] splitPixel = splitLine[1].split(",");
                    setPixelSizeX(Double.valueOf(splitPixel[0].trim().substring(1, splitPixel[0].length()-3)));
                    setPixelSizeY(Double.valueOf(splitPixel[1].trim().substring(0, splitPixel[0].length()-4)));
                } 
                else if(i == 9)
                {

                    String [] splitOscillation = splitLine[1].split("->");
                    setOscillationStart(Double.valueOf(splitOscillation[0].trim()));
                    setOscillationEnd(Double.valueOf(splitOscillation[1].trim().substring(0, splitOscillation[0].length()-4)));
                }      
                else if(i == 10)
                {

                    String theta = splitLine[1].substring(0, splitLine[1].length()-4).trim();

                    setTwoThetaValue(Double.valueOf(theta));
                }
            }   

            interval = getOscillationEnd() - getOscillationStart();
        }
        else
            
        {
            throw new Exception("Invalid image file");
        }  

        // TODO: Create validation on fields
      
    }
    
    public void displayVars()
    {
        System.out.println("Image type: " + getImageType());
        System.out.println("Collection Date (unix timestamp mm): " + getCollectionDate().getTime());
        System.out.println("Exposure time (seconds): " + getExposureTime());
        System.out.println("Detector S/N: " + getDetectorsn());
        System.out.println("Wavelength (Ang): " + getWavelength());
        System.out.println("Beam Center X (Ang): " + getBeamCenterX());
        System.out.println("Beam Center Y (Ang): " + getBeamCenterY());
        System.out.println("Distance To Detector (mm): " + getDistanceToDetector());
        System.out.println("Image Size X (px): " + getImageSizeX());
        System.out.println("Image Size Y (px): " + getImageSizeX());
        System.out.println("Pixel Size X (mm): " + getPixelSizeX());
        System.out.println("Pixel Size Y (mm): " + getPixelSizeY());
        System.out.println("Oscillation Start (phi, deg): " + getOscillationStart());
        System.out.println("Oscillation End (phi, deg): " + getOscillationEnd());
        System.out.println("Two Theta Value (deg): " + getTwoThetaValue());
        System.out.println("Osc Range Interval: " + getInterval());
    }

    public String getImageType() {
        return imageType;
    }

    public void setImageType(String imageType) {
        this.imageType = imageType;
    }

    public Date getCollectionDate() {
        return collectionDate;
    }

    public void setCollectionDate(Date collectionDate) {
        this.collectionDate = collectionDate;
    }

    public double getExposureTime() {
        return exposureTime;
    }

    public void setExposureTime(double exposureTime) {
        this.exposureTime = exposureTime;
    }

    public String getDetectorsn() {
        return detectorsn;
    }

    public void setDetectorsn(String detectorsn) {
        this.detectorsn = detectorsn;
    }

    public double getWavelength() {
        return wavelength;
    }

    public void setWavelength(double wavelength) {
        this.wavelength = wavelength;
    }

    public double getBeamCenterX() {
        return beamCenterX;
    }

    public void setBeamCenterX(double beamCenterX) {
        this.beamCenterX = beamCenterX;
    }

    public double getBeamCenterY() {
        return beamCenterY;
    }

    public void setBeamCenterY(double beamCenterY) {
        this.beamCenterY = beamCenterY;
    }

    public double getDistanceToDetector() {
        return distanceToDetector;
    }

    public void setDistanceToDetector(double distanceToDetector) {
        this.distanceToDetector = distanceToDetector;
    }

    public double getImageSizeX() {
        return imageSizeX;
    }

    public void setImageSizeX(double imageSizeX) {
        this.imageSizeX = imageSizeX;
    }

    public double getImageSizeY() {
        return imageSizeY;
    }
    
    public double getInterval() {
        return interval;
    }    

    public void setImageSizeY(double imageSizeY) {
        this.imageSizeY = imageSizeY;
    }

    public double getPixelSizeX() {
        return pixelSizeX;
    }

    public void setPixelSizeX(double pixelSizeX) {
        this.pixelSizeX = pixelSizeX;
    }

    public double getPixelSizeY() {
        return pixelSizeY;
    }

    public void setPixelSizeY(double pixelSizeY) {
        this.pixelSizeY = pixelSizeY;
    }

    public double getOscillationStart() {
        return oscillationStart;
    }

    public void setOscillationStart(double oscillationStart) {
        this.oscillationStart = oscillationStart;
    }

    public double getOscillationEnd() {
        return oscillationEnd;
    }

    public void setOscillationEnd(double oscillationEnd) {
        this.oscillationEnd = oscillationEnd;
    }

    public double getTwoThetaValue() {
        return twoThetaValue;
    }

    public void setTwoThetaValue(double twoThetaValue) {
        this.twoThetaValue = twoThetaValue;
    }
    
}
